package mailserver.server;

import java.io.*;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.charset.CharacterCodingException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.logging.*;
import mailcommon.entities.*;
import mailcommon.helpers.FunctionType;
import mailserver.server.exceptions.*;

public class ChannelOperator {

    private SocketChannel sc;
    private ClientService client;

    public ChannelOperator(SocketChannel sc)
            throws IOException {
        this.sc = sc;
        client = new ClientService();
        System.out.println("client connected");
    }

    public void run()
            throws CharacterCodingException, IOException, SQLException, ServerException {
        String s = readFromChannel();
        execute(FunctionType.parse(s));
    }

    private String readFromChannel() 
            throws IOException {
        ByteBuffer buf = ByteBuffer.allocate(10000);
        while (true) {
            sc.read(buf);
            buf.flip();
            String s = "";
            while (buf.hasRemaining()) {
                s += (char) buf.get();
            }
            if (!s.equalsIgnoreCase("")) {
                return s;
            }
        }
    }
    
    private Object readObject()
            throws CharacterCodingException, IOException {
        // read byte[] from channel 
        // -> create byte array input stream 
        // -> construct with it object input stream
        // -> read object
        String read = readFromChannel();
        ObjectInputStream ois = null;
        try {
        ByteArrayInputStream is = new ByteArrayInputStream(read.getBytes());
        ois = new ObjectInputStream(is);
        } catch (Exception ex) {
            Logger.getLogger(ChannelOperator.class.getName()).log(Level.SEVERE, null, ex);
        }
        Object obj = null;
        try {
            obj = ois.readObject();
        } catch (ClassNotFoundException ex) {
            Logger.getLogger(Server.class.getName()).log(Level.SEVERE, null, ex);
        }
        return obj;
    }

    private void writeToChannel(String s)
            throws IOException {
        sc.write(ByteBuffer.wrap(s.getBytes()));
    }

    private void writeObject(Object obj) 
            throws IOException {
        // creates new object output stream based on byte array output stream
        // write objects -> convert to byte[] -> write to channel
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(obj);
        oos.flush();
        baos.flush();
        ByteBuffer buf = ByteBuffer.wrap(baos.toByteArray());
        sc.write(buf);
        oos.close();
    }

    private void execute(FunctionType f)
            throws CharacterCodingException, IOException, SQLException, ServerException {
        if (f != null) {
            switch (f) {
                case Login: {
                    login();
                    break;
                }
                case Logout: {
                    logout();
                    break;
                }
                case CheckNewLetters: {
                    loadLetters(false);
                    break;
                }
                case SendLetter: {
                    sendLetter();
                    break;
                }
                case Registration: {
                    registration();
                    break;
                }
                case ReadLetter: {
                    readLetter();
                    break;
                }
                case LoadLetters: {
                    loadLetters(true);
                    break;
                }
            }
        } else {
            writeToChannel("server: unknown command");
        }
    }

    private void login()
            throws IOException {
        if (client.isLogged()) {
            writeToChannel(new UserLoggedInException().getMessage());
        } else {
            writeToChannel("OK");
            String address = readFromChannel();
            try {
                client.login(address);
                writeToChannel("Logged.");
            } catch (ServerException ex) {
                writeToChannel(ex.getMessage());
            } catch (SQLException ex) {
                Logger.getLogger(Server.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }

    private void logout()
            throws IOException {
        if (!client.isLogged()) {
            writeToChannel(new UserNotLoggedInException().getMessage());
        } else {
            writeToChannel("OK");
            try {
                client.logout();
                writeToChannel("Logged out.");
            } catch (UserNotLoggedInException ex) {
                writeToChannel(ex.getMessage());
            }
        }
    }

    private void loadLetters(boolean all)
            throws IOException, SQLException {
        if (!client.isLogged()) {
            writeToChannel(new UserNotLoggedInException().getMessage());
        } else {
            writeToChannel("OK");
            try {
                ArrayList<Letter> letters = client.getLetters(all);
                writeObject(letters);
            } catch (ServerException ex) {
                writeObject(ex.getMessage());
            }
        }
    }

    private void sendLetter()
            throws SQLException, IOException {
        if (!client.isLogged()) {
            writeToChannel(new UserNotLoggedInException().getMessage());
        } else {
            writeToChannel("OK");

            String s = (String)readObject();
            String[] vals = s.split("@@");
            String recipient = vals[0];
            String subject = vals[1];
            String body = vals[2];
            try {
                client.sendLetter(recipient, subject, body);
            } catch (ServerException ex) {
                writeToChannel(ex.getMessage());
            }
            writeToChannel("Letter sent.");
        }
    }

    private void registration()
            throws IOException, SQLException {
        if (client.isLogged()) {
            writeToChannel(new UserLoggedInException().getMessage());
        } else {
            writeToChannel("OK");

            User user = (User) readObject();
            String address = (String) readObject();
            try {
                client.registration(user, address);
                writeToChannel("Registration is successful.");
            } catch (ServerException ex) {
                writeToChannel(ex.getMessage());
            }
        }
    }

    private void readLetter()
            throws SQLException, ServerException, IOException {
        if (!client.isLogged()) {
            writeToChannel(new UserNotLoggedInException().getMessage());
        } else {
            writeToChannel("OK");
            try {
                int id = Integer.parseInt(readFromChannel());
                Letter letter = client.readLetter(id);
                writeObject(letter);
            } catch (LetterNotExistsException ex) {
                writeObject(ex.getMessage());
            }
        }
    }
}
